# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import math
import re

class Table(object):
  """
  This class allows you to format tables for console output.
  Example:
      table = Table(prefix="    ")
      table.header("equation", "result")
      table.row("1+2", "3")
      table.row("21*2", "42")
      print table
  The column width will automatically be determined to be fitting for every
  cell.
  """

  def __init__(self, prefix=""):
    """
    creates an empty table. Prefix will be printed before any new line
    of the table (i.e. it can be used to indent the table)
    """
    self.prefix = prefix
    self.title = None
    self.footer = None
    self.entries = []

  def header(self, *args):
    """
    Add a new table header row. Every parameter will be the title of one
    cell. Equivalent of <tr><th>arg1</th><th>arg2</th>...</tr>
    """
    self.entries.append(("header", args))

  def row(self, *args):
    """
    Add a new table data row. Every parameter will be the title of one
    cell. Equivalent of <tr><td>arg1</td><td>arg2</td>...</tr>
    """
    self.entries.append(("row", args))

  def __str__(self):
    """
    format the table using all entries added using header/row.
    """

    # The entries are structured in the following way:
    # self.entries = [
    #     ("header", ["header1", "header2", ...]),
    #     ("row", ["cell1", "cell2", ...]),
    #     ("row", ["cell1", "cell2", ...])
    # ]

    # calculate dimensions and number of columns
    column_count = max([len(entry[1]) for entry in self.entries])
    column_sizes = []
    for i in range(column_count):
      column_size = 2 + max([len(str(entry[1][i]))
                             for entry in self.entries
                             if i < len(entry[1])])
      column_sizes.append(column_size)

    # overall width of table including all decorations
    width = 4 + sum(column_sizes) + 3 * (column_count - 1);

    # format strings for all parts of the table
    title_format = self.prefix + "{0:^%d}" % width
    footer_format = (self.prefix + "| {0:^%d} |" % (width - 4) + "\n" +
                     self.prefix + "+-" + ("-" * (width - 4)) + "-+")

    th_format = (self.prefix + "+-" +
                 "-+-".join(["{%d:-^%d}" % (i, c)
                             for i, c in enumerate(column_sizes)]) +
                 "-+")
    td_format = (self.prefix + "| " +
                 " | ".join(["{%d:<%d}" % (i, c)
                             for i, c in enumerate(column_sizes)]) +
                 " |\x1b[0m")
    tf_format = (self.prefix + "+-" +
                 "-+-".join(["-"*c for c in column_sizes]) +
                 "-+")

    # build table from entries
    parts = []

    if self.title:
      parts.append(title_format.format(self.title))

    for entry in self.entries:
      if entry[0] == "header":
        format = th_format
      if entry[0] == "row":
        format = td_format

      # pad every element with one space
      data = []
      color = "0"
      for i in range(column_count):
        if i < len(entry[1]):
          value = str(entry[1][i])

          # add padding for console escape characters
          value = re.sub("(\x1b\[.m)",   "     \\1", value)
          value = re.sub("(\x1b\[..m)",  "    \\1", value)
          value = re.sub("(\x1b\[...m)", "   \\1", value)

          if "success" in value:
            color = "32"
          elif "failure" in value:
            color = "31"
          elif "bad" in value:
            color = "33"
          elif "disabled" in value or "incomplete" in value:
            color = "34"

          data.append(" " + value + " ")
        else:
          data.append("")

      line = format.format(*data)
      if color != "0":
        line = line.replace("|", "\x1b[m|\x1b[" + color + "m")

      parts.append(line)

    parts.append(tf_format)

    if self.footer:
      parts.append(footer_format.format(self.footer))

    return "\n".join(parts)

